/*  version: 20051227-2131 */
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <ctype.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>

#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <time.h>
#include <pthread.h>
#include <netinet/ip6.h>
#include <stdlib.h>

#include "tunnet.h"
#include "global.h"
#include "common.h"


//int neighbor_exchange(struct node *nbr);  
int neighbor_exchange();  

int neighbor_exchange() 
{
	struct node_item * np;

	np=neighbors.first;

	//if(!nbr) return -1;
        //np=nbr->first;

	while(np){
		if(np->nodeID==0 || np->sourceNode!=0) {
			np=np->next;
			continue; // my self
		}
		exchange(np);
		np=np->next;
	}	
	if(f_debug)neighbor_display(&neighbors);
	neighbor_dump(&neighbors);

	//alarm(TIMER_EXCHANGE);
	return 0;

} 

int exchange(struct node_item * np){
	
	struct sockaddr_in saddr;
	int sock,status;
	if(!np) return -1;
	
	debug("Exchange with neighbor[%d]\n", np->nodeID);
	sock=socket(AF_INET, SOCK_STREAM, 0);
	if(sock <0) {
		sys_log(info_log,"Error : create socket error(exchange)\n");
		return -1;
	}	
	saddr.sin_family=AF_INET;
	saddr.sin_addr.s_addr=np->ipAddress;		
	saddr.sin_port=np->controlPort;

	status=connect_nonb(sock,(struct sockaddr *)&saddr, sizeof(saddr),&timeout);
	if(status <0){
		debug("Connect_nonb failed in exchange() \n");
		close(sock);
		np->status=STATUS_UNKNOWN;	
		return -1;
	}
		
	np->status=STATUS_UNKNOWN;	
	//certificate exchange to be added here
	status=certificate_exchange(sock, np); 
	if(status <0){
		debug("Certificate exchange error in exchange(). \n");
		close(sock);
		np->status=STATUS_UNKNOWN;	
		return -1;
	}
	

	//Register myself  to my neighbor 
	status =node_register(sock, np);
		/* 0 : OK
		   -1: Reject or Error;
		    1: User exists already
		*/
	if(status ==-1){
		debug("node_register  error in exchange(). \n");
		close(sock);
		return -1;
	}

	//Now authentication process
	status=authentication_exchange(sock, np); 

	if(status <0){
		debug("Authentication error in exchange(). \n");
		np->status=STATUS_UNKNOWN;	
		close(sock);
		return -1;
	}

	if( status==CMD_SERVER_NOUSER){
		debug("Authentication error in exchange(): server reply that I have not register. \n");
		np->status=STATUS_UNKNOWN;	
		close(sock);
		return -1;
	}
	//np->status=STATUS_AUTHED;	
	np->status=STATUS_ACTIVE;	

	status=node_update(sock, np);
	if(status <0){
		debug("Node Exchange error in exchange(). \n");
		close(sock);
		return -1;
	}

	status=service_update(sock, np);
	if(status <0){
		debug("Service Update error in exchange(). \n");
		close(sock);
		return -1;
	}

	status=neighbor_request(sock, np);
	if(status <0){
		debug("neighbor request error in exchange(). \n");
		//close(sock);
		//return -1;
	}
	//neighbor_dump(&neighbors);
	status=route_update(sock, np);
	if(status <0){
		debug("Controller Exchange error in exchange(). \n");
		//close(sock);
		//return -1;
	}
	close(sock);
	return 0; 
}


/* Register a new node
        C->S: CMD_NODE_REGISTER
        +----1----+----1----+-----1----+-----/----+----1----+----/---+-----2-----+---1---+---/----+
        | VERSION | COMMAND | U_LENGTH | UserName | AddrType| IP_ADDR| CTRL_PORT | P_LEN | SECRET |
        +---------+---------+----------+----------+---------+--------+-----------+-------+--------+
                         
        If server accept this new node, it replys CMD_NODE_REGISTER_ACCEPT;
        otherwise, it replys CMD_NODE_REGISTER_DENY;
        S->C:
        +----1----+----1----+
        | VERSION | COMMAND |
        +---------+---------+
                
*/ 	
int node_register(int sock, struct node_item *np){
	PDU_HEADER ph;
	char *ptr , pdu[1024],*userName,*secret;
	int length,addrLen;
	
	struct node_item * me;
	me=neighbors.first;

        ph.version=VERSION_CTRL;
        ph.command=CMD_NODE_REGISTER;
	ATOMIC_OUT(sock,&ph, sizeof(ph), "Erorr Send pdu header in node_register()\n");	

        ptr=pdu; length=0;
        userName=me->userName;
        
        *ptr=(unsigned char)strlen(userName);
        ptr++;       
        strcpy(ptr,userName);
        ptr+=strlen(userName);
        
        *ptr=me->addrType;
        ptr++;
        addrLen=addrLength(me->addrType);

        memcpy(ptr,(void *)&me->ipAddress,addrLen);
        
        ptr+=addrLen;
        
        memcpy(ptr,(void *)&me->controlPort,2);
        ptr+=2;

	secret=np->secret;
	if(!strlen(secret)){
	     secret=me->secret;
	     strncpy(np->secret,me->secret,strlen(me->secret));
	}
	*ptr++=strlen(secret);

        memcpy(ptr,secret,strlen(secret));
        ptr+=strlen(secret);

        length=ptr-pdu;
	ATOMIC_OUT(sock,&pdu, length, "Erorr Send node_register information in node_register()\n");	
        debug("client has sent node update, over.\n");

	// Read the response from server

	ATOMIC_IN(sock,&ph, sizeof(ph), "Erorr read response  information in node_register()\n");	

	return 0;
		
}
int neighbor_request(int sock, struct node_item *np){
	    
    PDU_HEADER ph;
    int bytes,leng; 
    unsigned char u_len,addrType, status; 
    char  userName[MAX_USERNAME],ipAddress[128],ipstr[128];
    unsigned short controlPort;
    struct node_item  * nip;
    struct in_addr inaddr;

	
        ph.version=VERSION_CTRL;
        ph.command=CMD_NBR_REQUEST;

        bytes=atomic_out(sock, &ph, sizeof(ph));
        if(bytes != sizeof(ph)) {
                debug("error send response header :%d bytes send.\n",bytes);
                return -1;
        }

	u_len=0;
	atomic_out(sock, &u_len,1);


	// read from server ...

        bytes=atomic_in(sock, &ph, sizeof(ph));
        if(bytes != sizeof(ph)) {
                debug("error read response header :%d bytes read.\n",bytes);
                return -1;
        }

	bytes=atomic_in(sock, &u_len, 1);
	if(bytes != 1){
	   debug("read userName Length error\n");
	   return -1;
	}
        while(u_len){
		memset(userName,0,sizeof(userName));
                bytes=atomic_in(sock,userName,u_len);
		if(bytes != u_len){
		   debug("read userName error\n");
	   	   return -1;
		}
                bytes=atomic_in(sock,&addrType,1);
		if(bytes != 1){
		   debug("read addrType error\n");
	   	   return -1;
		}
		leng=addrLength(addrType);
		bytes=atomic_in(sock,&ipAddress,leng);
		if(bytes != leng){
		   debug("read ipAddress  error\n");
	   	   return -1;
		}
		bytes=atomic_in(sock,&controlPort,2);
		if(bytes != 2){
		   debug("read control Port error\n");
	   	   return -1;
		}
		inet_ntop(addrType,&ipAddress, ipstr, sizeof(ipstr)); // network byte order
		debug("inet_ntop ipstr=%s\n",ipstr);

		//memcpy(&inaddr, ipAddress,leng);
		//strcpy(ipstr, inet_ntoa(ntohl(inaddr.s_addr)));
		//debug("ntoh ipstr=%s\n",ipstr);
		//strcpy(ipstr, inet_ntoa(inaddr));
		//debug("no ntoh ipstr=%s\n",ipstr);

                bytes=atomic_in(sock,&status,1);
		if(bytes != 1){
		   debug("read Status  error in neighbor_exchange:neighbor_request()\n");
	   	   return -1;
		}

		debug("Got neighbors(%s:%d:%s:%d:%d)\n",userName,addrType,
			ipstr, controlPort,status);

		/*
		if(NULL==neighbor_lookup_byName(&neighbors,userName) ){
			nip=malloc(sizeof(struct node_item));	
			strcpy(nip->userName,userName);
			nip->addrType=addrType;
			memcpy(&nip->ipAddress, ipAddress, leng); //network byte order
			nip->controlPort=controlPort;
			nip->sourceNode=np->nodeID;
			nip->status=status;
			nip->secret[0]='\0';
			nip->next=NULL;
			nip->underFirewall=0;
			nip->services.first=NULL;
			nip->services.number=0;
			neighbor_insert(&neighbors,nip);
			// Don't save the neighbors of neigbhors
		}
		*/

		// Read Node Status


                bytes=atomic_in(sock,&u_len,1);
		if(bytes != 1){
		   debug("neighbor_request():read u_len error\n");
	   	   return -1;
		}
        }
	

}

int certificate_exchange(int sock, struct node_item *np)
{

}

int authentication_exchange(int sock, struct node_item *np)
{
/*
  C->S : CMD_CLIENT_HELLO
        +--------+--------+--------+----/-------+--------+------/-----+
        |VERSION |COMMAND |U_LENGTH| UserName   |NMETHODS|AUTH_METHODS|
        +--------+--------+--------+----/-------+--------+------------+
           1        1         1      U_LENGTH     1         NMETHODS
  S->C: CMD_SERVER_HELLO
        +--------+--------+--------+
        |VERSION |COMMAND | S_A_M  |
        +--------+--------+--------+
            1        1         1
        or( if S_A_M equal METHOD_CHAP or METHOD_X509):

        +--------+--------+--------+---------+------/-----+
        |VERSION |COMMAND | S_A_M  |C_LENGTH |  CHALLENGE |
        +--------+--------+--------+---------+------/-----+
            1        1         1        1        C_LENGTH

  C->S: CMD_CLIENT_AUTH

        +--------+--------+--------+---/------+
        |VERSION |COMMAND | P_LEN  | PASSWORD |
        +--------+--------+--------+---/------+
            1        1        1        1

  S->C: CMD_SERVER_CONF

        +--------+--------+--------+---/------+
        |VERSION |COMMAND | R_LEN  | RESPONSE |
        +--------+--------+--------+---/------+
            1         1       1        R_LEN
*/
	int status;
	PDU_HEADER ph;
	char pdu[1024], *ptr,*userName,*password;
	int  length, bytes;
	unsigned char u_length, n_methods;
	unsigned char selectedMethod;
	struct node_item *me;
	int succeed;
	
	me=neighbors.first;
	
	ph.version=VERSION_CTRL;	
	ph.command=CMD_CLIENT_HELLO;	
	
	bytes=atomic_out(sock,&ph, sizeof(ph));
	if(bytes != sizeof(ph)){
		debug("send pdu header error  in authentication_exchange()\n");	
		return -1;
	}
	userName=me->userName;

	password=np->secret;

	ptr=pdu; length=0;
	succeed=0;	

	*ptr=(unsigned char) strlen(userName);
	ptr++;	
	strcpy(ptr,userName);
	ptr+=strlen(userName);

	n_methods=2;
	*ptr++=n_methods;
	*ptr++=METHOD_NONE;
	*ptr++=METHOD_PASS;

	length=ptr-pdu;
	bytes=atomic_out(sock, pdu,(ptr-pdu));
	if(bytes != length) {
		debug(" sent client_hello Error . authentication_exchange()\n");
	}	

	bytes=atomic_in(sock,&ph, sizeof(ph));
	if(bytes != sizeof(ph)) {
		debug(" recieve server_hello pdu header Error. authentication_exchange()\n");
		return -1;
	}	
	if(ph.command ==CMD_SERVER_NOUSER){
		return CMD_SERVER_NOUSER;
	}
	if(ph.command != CMD_SERVER_HELLO){
		debug(" CMD_SERVER_HELLO expected, but got:%d. authentication_exchange()\n",
			ph.command);
		return -1;
	}  
	bytes=atomic_in(sock,&selectedMethod, 1);
	if(bytes != 1) {
		debug(" recieve S_A_M Error. authentication_exchange()\n");
		return -1;
	}
	switch(selectedMethod){
	    case METHOD_NONE:
		 succeed=1;

	    break;
	    case METHOD_PASS:
        	ph.version=VERSION_CTRL;
        	ph.command=CMD_CLIENT_AUTH;
        	bytes=atomic_out(sock,&ph, sizeof(ph));
        	if(bytes != sizeof(ph)){
                	debug("send pdu header (password) error  in authentication_exchange()\n");
                	return -1;
        	}	

		memset(pdu,0,sizeof(pdu));
		ptr=pdu;
		*ptr++=strlen(password);
		strcpy(ptr,password);
		u_length=strlen(password)+1;	

        	bytes=atomic_out(sock,pdu,u_length);
        	if(bytes != u_length){
                	debug("send password error  in authentication_exchange()\n");
                	return -1;
        	}	
		
		// read CMD_SERVER_CONF 
		bytes=atomic_in(sock, &ph, sizeof(ph));
		if(bytes != sizeof(ph)){
			debug("read pdu header error (read CMD_SERVER_CONF)\n");
			return -1;
		}	

		if(ph.command ==CMD_SERVER_CONF) {
			debug("Authentication OK\n");	
		 	succeed=1;
		}
		else if(ph.command==CMD_SERVER_ERROR){
			debug("Authentication failed, password missmatch.\n");
		 	succeed=0;
		}
	    break;

	    case METHOD_X509:
	    case METHOD_CHAP:
	    default: 
		debug("Authentication Method Not supported now\n");
		succeed =0;
	    break;
	}
	
	if(succeed){
		np->status=STATUS_AUTHED;	
		return 0;
	}else	return -1;
	
}

/****************** NODE UPDATE ******************

  C->S: CMD_NODE_UPDATE
        +--------+--------+--------+-----/----+--------+----/-----+
        |VERSION |COMMAND |U_LEN   |USERNAME  |addrType|ipAddress |
        +--------+--------+--------+-----/----+--------+----/-----+
            1        1         1      U_LEN       1        4 or 16
        ----------+-----------+
        CTRL_PORT |addrType(0)|
        ----------+-----------+
             2         1
    
  S->C: CMD_NODE_ACCEPT
        +--------+--------+--------+-----/----+--------+----/-----+
        |VERSION |COMMAND |U_LEN   |USERNAME  |addrType|ipAddress |
        +--------+--------+--------+-----/----+--------+----/-----+
            1        1         1      U_LEN       1      4 or 16
        ----------+-----------+
        CTRL_PORT |addrType(0)|
        ----------+-----------+
             2         1
        
  S->C: CMD_NODE_REJECT
                         
        0         8       16
        +--------+--------+
        |VERSION |COMMAND |
        +--------+--------+
****************************************************/
int node_update(int sock, struct node_item *np)
{
     
    PDU_HEADER ph;
    unsigned char version, command, u_length, addrType,tail;
    unsigned int controlPort;
    char ipAddress[128];
    int bytes, i, nodeID;
    struct node_item * me;
    int  addrLen,length;
    char pdu[1024], *ptr,*userName,inUserName[MAX_USERNAME];


	me=neighbors.first;
	memset(pdu,0,sizeof(pdu));
	ph.version=VERSION_CTRL;
        ph.command=CMD_NODE_UPDATE;

        bytes=atomic_out(sock,&ph, sizeof(ph));
        if(bytes != sizeof(ph)){
                debug("send pdu header error  in node_update()\n");
                return -1;
        }

        ptr=pdu; length=0;
        userName=me->userName;
	addrType=me->addrType;

	*ptr=(unsigned char)strlen(userName);
	ptr++;
	strcpy(ptr,userName);
	ptr+=strlen(userName);
	
	*ptr=me->addrType;
	ptr++;
	addrLen=addrLength(me->addrType);

	memcpy(ptr,(void *)&me->ipAddress,addrLen);

	ptr+=addrLen;

	memcpy(ptr,(void *)&me->controlPort,2);
	ptr+=2;
	*ptr=0;

	 ptr++;

        length=ptr-pdu;
        bytes=atomic_out(sock, pdu,(ptr-pdu));
        if(bytes != length) {
                debug(" sent node update Error . node_update()\n");
		return -1;
        }       
	debug("client has sent node update, over.\n");
// receive server update 
	        
	debug("client waiting for server's node update\n");
        bytes=atomic_in(sock,&ph, sizeof(ph));
        if(bytes != sizeof(ph)) {     
                debug(" get node update reply Error . node_update()\n");
                return -1;
        }       
        
	if(ph.command != CMD_NODE_ACCEPT){
                debug("CMD_NODE_ACCEPT expected, but got:%d. authentication_exchange()\n",
                        ph.command);
                return -1;
        }  

	memset(inUserName,0, sizeof(inUserName));

        bytes=atomic_in(sock, &u_length, 1);
        if(bytes!=1) {
                debug("Error read U_LENGTH. s_node_update()\n");
                return -1;
        }       
        bytes=atomic_in(sock, inUserName, u_length);
        if(bytes!=u_length) {
                debug("Error read userName . s_node_update()\n");
                return -1;
        }
        bytes=atomic_in(sock, &addrType, 1);
        if(bytes!=1) {
                debug("Error read addrType . s_node_update()\n");
                return -1;
        }
        addrLen=addrLength(addrType);
        bytes=atomic_in(sock, ipAddress, addrLen);
        if(bytes!=addrLen) {
                debug("Error read ipAddress . s_node_update()\n");
                return -1;
        }

	bytes=atomic_in(sock, &controlPort, 2);
        if(bytes!=2) {
                debug("Error read controlPort. s_node_update()\n");
                return -1;
        }
        bytes=atomic_in(sock, &tail, 1);
        if(bytes!=1) {
                debug("Error read Tail. s_node_update()\n");
                return -1;
        }
	debug("client has got node update from the server, over.\n");
//update local information;
	 neighbor_update(np->nodeID,inUserName,addrType,ipAddress, controlPort);

        return 0;
}



/**********************************************************

                  
        +--------+--------+--------+---------+---------+---------+~~
        |VERSION |COMMAND |srvcType|relayPort|srvcType |relayPort|~
        +--------+--------+--------+---------+---------+---------+~~
           1         1         1       1         4 or 16     2
           
        ~--------+---------+-----------+
         srvcType|relayPort|srvcType(0)|
        ~--------+---------+-----------+
             1       1        4 or 16   

**********************************************************/

int service_update(int sock, struct node_item *np)
{
    PDU_HEADER ph;
    unsigned char  n_services,srvcType, tail;
    int bytes, length;
    struct node_item * me;
    char pdu[1024],*ptr;
    struct service_item * sp;
    unsigned short relayPort;

    unsigned short port_com;

    debug("begin service_update()\n");

    me=neighbors.first;
    sp=me->services.first;
    n_services=me->services.number;
    //if(n_services<=0) return 0;
     
    memset(pdu,0,sizeof(pdu));
    ptr=pdu;
    length=0;

    while(sp){
	if(sp->status ==SRVC_STAT_START) {
		*ptr=sp->srvcType;
		ptr++;
		memcpy(ptr,(void*)&(sp->relayPort),2);
		ptr+=2;
	}
	sp=sp->next;								
    }
    *ptr=0;
     ptr++;

    length=ptr-pdu;

    ph.version=VERSION_CTRL;
    ph.command=CMD_SRVC_UPDATE;

    bytes=atomic_out(sock,&ph, sizeof(ph));
    if(bytes != sizeof(ph)){
           debug("send pdu header error  in service_update()\n");
           return -1;
    }

    bytes=atomic_out(sock, pdu, length);
    if(bytes != length){
           debug("send pdu  error  in service_update()\n");
           return -1;
    }

    ///////////////////////////////////////////////////////////////
    //   get services from remote ////////////

    debug("waiting for server update...\n");

    bytes=atomic_in(sock,&ph, sizeof(ph));
    if(bytes != sizeof(ph)) {
            debug("get php_header error . service_update()\n");
            return -1;
    }
    if(ph.command == CMD_SRVC_ACCEPT || ph.command == CMD_SRVC_REVERSE){
    	bytes=atomic_in(sock,&srvcType,1);
    	if(bytes !=1) { sys_log(info_log, "read srvcType error. s_service_update()\n");return -1;}

    	while(srvcType){
            bytes=atomic_in(sock,&relayPort,2);
            if(bytes !=2) {
               sys_log(info_log, "read relayPort error. s_service_update()\n");
               return -1;
            }
            service_update_nbr(np->nodeID,srvcType,relayPort);

            bytes=atomic_in(sock,&srvcType,1);
            if(bytes !=1) {
               sys_log(info_log, "read srvcType error. service_update()\n");
               return -1;
            }
        }
    	debug("I have got service update from server.\n");

    	if(ph.command == CMD_SRVC_REVERSE){
		ATOMIC_IN(sock, &port_com,2,"Read  Port_com Error in control_client.c:service_update()\n");
		debug("The remote peer request to set up a reverse connection on port :%d\n", ntohs(port_com));
    		sp=me->services.first;
		if(sp) reverse_connect_inner(np->ipAddress,sp->relayPort,port_com); //port_com is in network order	?
		
    	}
	return 0;
    }

    debug("CMD_SRVC_ACCEPT || REVERSE expected, but got:%d. service_update()\n",
            ph.command);
    return -1;
}

int route_update(int sock, struct node_item *np)
{
	PDU_HEADER ph;
    	int bytes;
    	unsigned char netType;
	int netLen;
	char netAddr[128];
	unsigned int prefix,hops;
	unsigned long bandwidth;
	char pdu[1024],*ptr;
	int length;

	struct route_item* rip;
	
    	ph.version=VERSION_CTRL;
    	ph.command=CMD_ROUTE_UPDATE;

    	//bytes=Send(sock,(char*)&ph, sizeof(ph));
	ATOMIC_OUT(sock,&ph, sizeof(ph), "Send route update PDU Error.\n" );

	memset(pdu,0,sizeof(pdu));
    	length=0;
	rip=routes.first;
	ptr=pdu;	
	
/* 
	+-------+----------+-------+-----+----------+-------+...+---------+----+
	|netType|netAddress| prefix|hops | bandwidth|netType|...|bandwidth|  0 |
	+-------+----------+-------+-----+----------+-------+---+---------+----+   
	   1         4/6       1      1    2
*/
	while (rip)
	{
		*ptr=rip->netType;
		ptr++;

		netLen=addrLength(rip->netType);
		memcpy(ptr,(void*)&(rip->netAddress),netLen);
		ptr+=netLen;

		*ptr=rip->prefix;
		ptr++;

		*ptr=rip->hops;
		ptr++;

		memcpy(ptr,(void*)&(rip->bandwidth),2);
		ptr+=2;

		rip=rip->next;
	}
	*ptr++=0;
	length=ptr-pdu;
	ATOMIC_OUT(sock, pdu,length ,"Error on send route update \n");		
	debug("route update OK\n");
	return;
}
